Autor:
    Data utworzenia:2025-09-09Ostatnia aktualizacja:2026-08-25

    Przetłumacz swoją stronę Tanstack Start za pomocą Intlayer | Internacjonalizacja (i18n)

    Spis treści

    Ten przewodnik pokazuje, jak zintegrować Intlayer dla płynnej internacjonalizacji w projektach Tanstack Start z routingiem uwzględniającym lokalizację, wsparciem TypeScript oraz nowoczesnymi praktykami programistycznymi.

    Dlaczego Interlayer zamiast alternatyw?

    W porównaniu do głównych rozwiązań, takich jak „react-i18next”, „use-intl” lub „paraglide”, Intlayer jest rozwiązaniem wyposażonym w zintegrowane optymalizacje, takie jak:

    Intlayer jest w pełni zoptymalizowany pod kątem TanStack Start, zapewniając wielojęzyczny routing, zarządzanie plikami cookie, generowanie mapy witryny, dynamiczne ładowanie treści i wszystkie funkcje potrzebne do skalowania wysiłków związanych z internacjonalizacją (i18n).

    Zamiast ładować ogromne pliki JSON na swoje strony, ładuj tylko niezbędną treść. Intlayer pomaga zmniejszyć rozmiary bundle'a i stron nawet o 50%.

    Określanie zakresu zawartości aplikacji ułatwia konserwację aplikacji na dużą skalę. Możesz powielić lub usunąć pojedynczy folder funkcji bez obciążania psychicznego koniecznością przeglądania całej bazy kodu zawartości. Dodatkowo Inlayer jest w pełni otypowany, aby zapewnić dokładność treści.

    Wspólna lokalizacja treści zmniejsza potrzebny kontekst dzięki modelom dużego języka (LLM). Intlayer zawiera także zestaw narzędzi, taki jak CLI do sprawdzania brakujących tłumaczeń, LSP, MCP i agent skills, aby praca programisty (DX) była jeszcze płynniejsza dla agentów AI.

    Korzystaj z automatyzacji, aby tłumaczyć w swoim potoku CI/CD przy użyciu wybranego LLM na koszt dostawcy sztucznej inteligencji. Intlayer oferuje także kompilator do automatyzacji ekstrakcji treści, a także platformę internetową, która pomaga tłumaczyć w tle.

    Łączenie ogromnych plików JSON z komponentami może prowadzić do problemów z wydajnością i reaktywnością. Inlayer optymalizuje ładowanie treści w czasie kompilacji.

    Więcej niż tylko rozwiązanie i18n, Intlayer zapewnia samodzielny edytor wizualny i pełny CMS, który pomoże Ci zarządzać wielojęzyczną treścią w w czasie rzeczywistym, dzięki czemu współpraca z tłumaczami, copywriterami i innymi członkami zespołu będzie płynna. Treść może być przechowywana lokalnie i/lub zdalnie.

    Przewodnik krok po kroku, jak skonfigurować Intlayer w aplikacji Tanstack Start

    youtube.com
    ide.intlayer.org
    intlayer-tanstack-start-template.vercel.app

    Zobacz Szablon aplikacji na GitHub.

    1. Utwórz projekt

      Rozpocznij od utworzenia nowego projektu TanStack Start, postępując zgodnie z przewodnikiem Start new project na stronie TanStack Start.

    2. Zainstaluj pakiety Intlayer

      Zainstaluj niezbędne pakiety, używając preferowanego menedżera pakietów:

      bash
      npx intlayer init --interactive
      
      flaga --interactive jest opcjonalna. Użyj intlayer-cli init, jeśli jesteś agentem AI.
      To polecenie wykryje Twoje środowisko i zainstaluje wymagane pakiety. Na przykład:
      bash
      npm install intlayer react-intlayer
      npm install vite-intlayer --save-dev
      
      • intlayer

        Podstawowy pakiet, który dostarcza narzędzia do internacjonalizacji dla zarządzania konfiguracją, tłumaczeń, deklaracji treści, transpiliacji oraz poleceń CLI.

      • react-intlayer Pakiet integrujący Intlayer z aplikacją React. Zapewnia dostawców kontekstu oraz hooki do internacjonalizacji w React.

      • vite-intlayer Zawiera wtyczkę Vite do integracji Intlayer z bundlerem Vite, a także middleware do wykrywania preferowanego języka użytkownika, zarządzania ciasteczkami oraz obsługi przekierowań URL.

    3. Konfiguracja projektu

      Architektura

      W tej architekturze wszystkie zlokalizowane trasy są zagnieżdżone w segmencie trasy {-$locale}. Takie podejście gwarantuje, że każdy język ma dedykowany adres URL, umożliwiając jednocześnie automatyczne dodawanie prefiksu ustawień regionalnych, walidację i optymalizację SEO.

      bash
      .
      ├── src
         ├── components
         ├── Header.tsx
         ├── locale-switcher.content.ts
         ├── locale-switcher.tsx
         └── localized-link.tsx
         ├── hooks
         ├── useI18nHTMLAttributes.tsx
         └── useLocalizedNavigate.ts
         ├── routes
         ├── {-$locale}
         ├── 404.content.ts
         ├── 404.tsx
         ├── about.content.ts
         ├── about.tsx
         ├── index.content.tsx
         ├── index.tsx
         └── route.tsx             # Locale layout & prefix validation
         ├── __root.tsx                # Root route with IntlayerProvider
         └── sitemap[.]xml.ts
         ├── router.tsx
         └── styles.css
      ├── intlayer.config.ts
      ├── package.json
      ├── tsconfig.json
      └── vite.config.ts
      

      Konfiguracja

      Utwórz plik konfiguracyjny, aby skonfigurować języki swojej aplikacji:

      intlayer.config.ts
      import type { IntlayerConfig } from "intlayer";
      
      import { Locales } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          defaultLocale: Locales.ENGLISH,
          locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH],
        },
      };
      
      export default config;
      
      Za pomocą tego pliku konfiguracyjnego możesz ustawić lokalizowane adresy URL, przekierowania w middleware, nazwy ciasteczek, lokalizację i rozszerzenie deklaracji treści, wyłączyć logi Intlayer w konsoli i wiele więcej. Pełną listę dostępnych parametrów znajdziesz w dokumentacji konfiguracji.
    4. Integracja Intlayer w konfiguracji Vite

      Dodaj wtyczkę intlayer do swojej konfiguracji:

      vite.config.ts
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      const config = defineConfig({
        plugins: [
          nitro(),
          intlayer({
            proxy: {
              ignore: (req) => req.url?.startsWith("/api"),
            },
          }),
          tanstackStart({
            router: {
              routeFileIgnorePattern:
                ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5|md|mdx|yaml|yml)$",
            },
          }),
          viteReact(),
        ],
      });
      
      export default config;
      
      Wtyczka intlayer() dla Vite służy do integracji Intlayer z Vite. Zapewnia budowanie plików deklaracji treści oraz monitorowanie ich w trybie deweloperskim. Definiuje zmienne środowiskowe Intlayer w aplikacji Vite. Dodatkowo dostarcza aliasy optymalizujące wydajność.
    5. Utwórz układ główny

      Skonfiguruj swój główny układ, aby wspierać internacjonalizację, używając useParams do wykrywania aktualnej lokalizacji i ustawiając atrybuty lang i dir w tagu html.

      src/routes/__root.tsx
      import {
        createRootRouteWithContext,
        getRouteApi,
        HeadContent,
        Scripts,
      } from "@tanstack/react-router";
      import { defaultLocale, getHTMLTextDir } from "intlayer";
      import { type ReactNode } from "react";
      import { IntlayerProvider } from "react-intlayer";
      
      const localeRoute = getRouteApi("/{-$locale}");
      
      export const Route = createRootRouteWithContext<{}>()({
        head: () => ({
          meta: [
            {
              charSet: "utf-8",
            },
            {
              content: "width=device-width, initial-scale=1",
              name: "viewport",
            },
            {
              title: "TanStack Start Starter",
            },
          ],
        }),
      
        shellComponent: RootDocument,
      });
      
      function RootDocument({ children }: { children: ReactNode }) {
        const params = localeRoute.useParams();
        const locale = params?.locale ?? defaultLocale;
      
        return (
          <html dir={getHTMLTextDir(locale)} lang={locale}>
            <head>
              <HeadContent />
            </head>
            <body>
              <IntlayerProvider locale={locale}>{children}</IntlayerProvider>
              <Scripts />
            </body>
          </html>
        );
      }
      
    6. Utwórz układ lokalizacji

      Utwórz układ, który obsługuje prefiks lokalizacji i wykonuje walidację.

      src/routes/{-$locale}/route.tsx
      import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
      import { validatePrefix } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}")({
        beforeLoad: ({ params }) => {
          const localeParam = params.locale;
      
          // Walidacja prefiksu lokalizacji
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
            });
          }
        },
        component: Outlet,
      });
      
      Tutaj {-$locale} jest dynamicznym parametrem trasy, który zostaje zastąpiony aktualną lokalizacją. Ta notacja sprawia, że slot jest opcjonalny, co pozwala na współpracę z trybami routingu takimi jak 'prefix-no-default' itp.

      Pamiętaj, że ten slot może powodować problemy, jeśli używasz wielu dynamicznych segmentów w tej samej trasie (np. /{-$locale}/other-path/$anotherDynamicPath/...). W trybie 'prefix-all' możesz woleć zmienić slot na $locale. W trybach 'no-prefix' lub 'search-params' możesz całkowicie usunąć ten slot.

    7. Zadeklaruj swoją treść

      Twórz i zarządzaj deklaracjami treści, aby przechowywać tłumaczenia:

      src/contents/page.content.ts
      import type { Dictionary } from "intlayer";
      
      import { t } from "intlayer";
      
      const appContent = {
        content: {
          links: {
            about: t({
              en: "About",
              es: "Acerca de",
              fr: "À propos",
            }),
            home: t({
              en: "Home",
              es: "Inicio",
              fr: "Accueil",
            }),
          },
          meta: {
            title: t({
              en: "Welcome to Intlayer + TanStack Router",
              es: "Bienvenido a Intlayer + TanStack Router",
              fr: "Bienvenue à Intlayer + TanStack Router",
            }),
            description: t({
              en: "This is an example of using Intlayer with TanStack Router",
              es: "Este es un ejemplo de uso de Intlayer con TanStack Router",
              fr: "Ceci est un exemple d'utilisation d'Intlayer avec TanStack Router",
            }),
          },
        },
        key: "app",
      } satisfies Dictionary;
      
      export default appContent;
      
      Twoje deklaracje zawartości mogą być definiowane w dowolnym miejscu w Twojej aplikacji, pod warunkiem, że zostaną umieszczone w katalogu contentDir (domyślnie ./app). I będą miały rozszerzenie pliku deklaracji zawartości (domyślnie .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml}).
      Po więcej szczegółów odsyłamy do dokumentacji deklaracji zawartości.
    8. Tworzenie komponentów i hooków uwzględniających lokalizację

      Utwórz komponent LocalizedLink do nawigacji uwzględniającej lokalizację:

      src/components/localized-link.tsx
      import type { FC } from "react";
      
      import { Link, type LinkComponentProps } from "@tanstack/react-router";
      import { useLocale } from "react-intlayer";
      import { getPrefix } from "intlayer";
      
      export const LOCALE_ROUTE = "{-$locale}" as const;
      
      export type To = StripLocalePrefix<LinkComponentProps["to"]>;
      
      export type StripLocalePrefix<T extends string | undefined> = T extends
        `/${typeof LOCALE_ROUTE}/` | `/${typeof LOCALE_ROUTE}`
        ? "/"
        : T extends `/${typeof LOCALE_ROUTE}/${infer Rest}`
          ? `/${Rest}`
          : T;
      
      type LocalizedLinkProps = {
        to?: To;
      } & Omit<LinkComponentProps, "to">;
      
      export const LocalizedLink: FC<LocalizedLinkProps> = (props) => {
        const { locale } = useLocale();
        const { localePrefix } = getPrefix(locale);
      
        return (
          <Link
            {...props}
            params={{
              locale: localePrefix,
              ...(typeof props?.params === "object" ? props?.params : {}),
            }}
            to={`/${LOCALE_ROUTE}${props.to}` as LinkComponentProps["to"]}
          />
        );
      };
      

      Ten komponent ma dwa cele:

      • Usunięcie niepotrzebnego prefiksu {-$locale} z URL.
      • Wstrzyknięcie parametru locale do URL, aby zapewnić użytkownikowi bezpośrednie przekierowanie do zlokalizowanej ścieżki.

      Następnie możemy stworzyć hook useLocalizedNavigate do nawigacji programowej:

      src/hooks/useLocalizedNavigate.tsx
      import { useNavigate } from "@tanstack/react-router";
      import { getPrefix } from "intlayer";
      import { useLocale } from "react-intlayer";
      import type { StripLocalePrefix } from "@/components/localized-link";
      import type { FileRouteTypes } from "@/routeTree.gen";
      
      type NavigateFn = ReturnType<typeof useNavigate>;
      type BaseNavigateOptions = Parameters<NavigateFn>[0];
      
      type LocalizedTo = StripLocalePrefix<FileRouteTypes["to"]>;
      
      export type LocalizedNavigateOptions = Omit<
        BaseNavigateOptions,
        "to" | "params"
      > & {
        to: LocalizedTo;
        params?: Omit<NonNullable<BaseNavigateOptions["params"]>, "locale">;
      };
      
      type LocalizedNavigate = (
        options: LocalizedNavigateOptions
      ) => ReturnType<NavigateFn>;
      
      export const useLocalizedNavigate = () => {
        const navigate = useNavigate();
      
        const { locale } = useLocale();
      
        const localizedNavigate: LocalizedNavigate = (args: any) => {
          const { localePrefix } = getPrefix(locale);
      
          if (typeof args === "string") {
            return navigate({
              to: `/${LOCALE_ROUTE}${args}`,
              params: { locale: localePrefix },
            });
          }
      
          const { to, ...rest } = args;
      
          const localizedTo = `/${LOCALE_ROUTE}${to}` as any;
      
          return navigate({
            to: localizedTo,
            params: { locale: localePrefix, ...rest } as any,
          });
        };
      
        return localizedNavigate;
      };
      
    9. Wykorzystaj Intlayer na swoich stronach

      Domyślnie używaj useIntlayer: to zalecany sposób odczytu treści wewnątrz komponentów, a kompilator rozwiązuje go do renderowanej lokalizacji. Po getIntlayer / getIntlayerAsync sięgaj tylko poza drzewem React: w head tras, loaderach i funkcjach serwerowych.

      Uzyskaj dostęp do swoich słowników treści w całej aplikacji:

      Strona główna zlokalizowana

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import { useIntlayer } from "react-intlayer";
      
      import LocaleSwitcher from "@/components/locale-switcher";
      import { LocalizedLink } from "@/components/localized-link";
      import { useLocalizedNavigate } from "@/hooks/useLocalizedNavigate";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
      });
      
      function RouteComponent() {
        const content = useIntlayer("app");
        const navigate = useLocalizedNavigate();
      
        return (
          <div>
            <div>
              {content.title}
              <LocaleSwitcher />
              <div>
                <LocalizedLink to="/">{content.links.home}</LocalizedLink>
                <LocalizedLink to="/about">{content.links.about}</LocalizedLink>
              </div>
              <div>
                <button onClick={() => navigate({ to: "/" })}>
                  {content.links.home}
                </button>
                <button onClick={() => navigate({ to: "/about" })}>
                  {content.links.about}
                </button>
              </div>
            </div>
          </div>
        );
      }
      

      Jeśli chcesz użyć zawartości w atrybucie string, takim jak alt, title, href, aria-label itd., możesz użyć wartości funkcji, na przykład:

      tsx
      <img src={content.image.src.value} alt={content.image.value} />
      <img src={content.image.src.toString()} alt={content.image.toString()} />
      <img src={String(content.image.src)} alt={String(content.image)} />
      
      Aby dowiedzieć się więcej o hook'u useIntlayer, zapoznaj się z dokumentacją.
    10. Utwórz komponent przełącznika języków

      Utwórz komponent umożliwiający użytkownikom zmianę języka:

      src/components/locale-switcher.tsx
      import { useLocation } from "@tanstack/react-router";
      import {
        getHTMLTextDir,
        getLocaleName,
        getPathWithoutLocale,
        getPrefix,
        Locales,
      } from "intlayer";
      import type { FC } from "react";
      import { useLocale } from "react-intlayer";
      
      import { LocalizedLink, type To } from "./localized-link";
      
      export const LocaleSwitcher: FC = () => {
        const { pathname } = useLocation();
      
        const { availableLocales, locale, setLocale } = useLocale();
      
        const pathWithoutLocale = getPathWithoutLocale(pathname);
      
        return (
          <ol>
            {availableLocales.map((localeEl) => (
              <li key={localeEl}>
                <LocalizedLink
                  aria-current={localeEl === locale ? "page" : undefined}
                  onClick={() => setLocale(localeEl)}
                  params={{ locale: getPrefix(localeEl).localePrefix }}
                  to={pathWithoutLocale as To}
                >
                  <span>
                    {/* Locale - np. FR */}
                    {localeEl}
                  </span>
                  <span>
                    {/* Język w jego własnym locale - np. Français */}
                    {getLocaleName(localeEl, locale)}
                  </span>
                  <span dir={getHTMLTextDir(localeEl)} lang={localeEl}>
                    {/* Język w bieżącym locale - np. Francés przy bieżącym locale ustawionym na Locales.SPANISH */}
                    {getLocaleName(localeEl)}
                  </span>
                  <span dir="ltr" lang={Locales.ENGLISH}>
                    {/* Język w angielskim - np. French */}
                    {getLocaleName(localeEl, Locales.ENGLISH)}
                  </span>
                </LocalizedLink>
              </li>
            ))}
          </ol>
        );
      };
      
      Aby dowiedzieć się więcej o hook'u useLocale, zapoznaj się z dokumentacją.
    11. Zarządzanie atrybutami HTML

      Jak pokazano w kroku 5, możesz zarządzać atrybutami lang i dir tagu html za pomocą useParams w komponencie głównym. Zapewnia to, że prawidłowe atrybuty są ustawione na serwerze i kliencie.

      src/routes/__root.tsx
      const localeRoute = getRouteApi("/{-$locale}");
      
      function RootDocument({ children }: { children: ReactNode }) {
        const params = localeRoute.useParams();
        const locale = params?.locale ?? defaultLocale;
      
        return (
          <html dir={getHTMLTextDir(locale)} lang={locale}>
            {/* ... */}
          </html>
        );
      }
      
    12. Dodaj middleware

      Możesz także użyć intlayerProxy do dodania routingu po stronie serwera do aplikacji. Plugin ten automatycznie wykryje bieżący język na podstawie URL i ustawi odpowiedni plik cookie języka. Jeśli nie zostanie określony żaden język, plugin określi najbardziej odpowiedni język na podstawie preferencji języka przeglądarki użytkownika. Jeśli nie zostanie wykryty żaden język, nastąpi przekierowanie do języka domyślnego.

      Uwaga: aby użyć intlayerProxy w środowisku produkcyjnym, musisz przenieść pakiet vite-intlayer z devDependencies do dependencies.
      Od Intlayer v9, intlayerProxy() jest bezpośrednio dołączony do pluginu intlayer() i domyślnie włączony za pośrednictwem opcji routing.enableProxy (true domyślnie). Rejestrowanie go osobno, jak pokazano poniżej, jest teraz opcjonalne: jest zachowywane dla kompatybilności wstecznej i dla ustawień, które muszą kontrolować kolejność pluginów. Ustaw routing.enableProxy: false, aby zrezygnować. Zapoznaj się z notatkami do wydania v9.
      vite.config.ts
      import { tanstackStart } from "@tanstack/react-start/plugin/vite";
      import viteReact from "@vitejs/plugin-react";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [
          nitro(),
          intlayer({
            proxy: {
              ignore: (req) => req.url?.startsWith("/api"),
            },
          }),
          tanstackStart({
            router: {
              routeFileIgnorePattern:
                ".content.(ts|tsx|js|mjs|cjs|jsx|json|jsonc|json5|md|mdx|yaml|yml)$",
            },
          }),
          viteReact(),
        ],
      });
      
    13. Internationalizuj swoje metadane

      getIntlayer rozwiązuje się synchronicznie względem scalonego słownika, tego zawierającego każdy zadeklarowany język. head pozostaje synchroniczny i nic nie jest oczekiwane, ale cały wielojęzyczny słownik jest pobierany do fragmentu trasy wysyłanego do przeglądarki.

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import {
        defaultLocale,
        getIntlayer,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        head: ({ params }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // Ścieżka dla tej trasy
      
          const metaContent = getIntlayer("app", locale);
      
          return {
            links: [
              // Link kanoniczny: wskazuje na bieżącą stronę zlokalizowaną
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: powiadomi Google o wszystkich zlokalizowanych wersjach
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: dla użytkowników w niedopasowanych językach
              // Zdefiniuj domyślny fallback locale (zwykle Twój język podstawowy)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: metaContent.title },
              { name: "description", content: metaContent.meta.description },
            ],
          };
        },
      });
      

      Najlepsze dla małych słowników metadanych, kilku locale'i lub podczas prototypowania.

      getIntlayerAsync (dostępne od v9.4) zachowuje się jak getIntlayer, ale plugin budowania wskazuje go na fragment dla konkretnego locale'a w .intlayer/dynamic_dictionaries/ zamiast scalonego słownika. Strona zatem wysyła tylko locale, który renderuje. Ponieważ fragment jest ładowany na żądanie, head staje się async:

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import {
        defaultLocale,
        getIntlayerAsync,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        head: async ({ params }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // Ścieżka dla tej trasy
      
          const metaContent = await getIntlayerAsync("app", locale);
      
          return {
            links: [
              // Link kanoniczny: wskazuje na bieżącą stronę zlokalizowaną
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: powiadomi Google o wszystkich zlokalizowanych wersjach
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: dla użytkowników w niedopasowanych językach
              // Zdefiniuj domyślny fallback locale (zwykle Twój język podstawowy)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: metaContent.title },
              { name: "description", content: metaContent.meta.description },
            ],
          };
        },
      });
      
      Jeśli head odczytuje kilka słowników, rozwiąż je za pomocą Promise.all: oczekiwanie każdego getIntlayerAsync w oddzielnej linii łańcuchuje żądania zamiast uruchamiać je równolegle.

      Kompromis: import dynamiczny jest rozwiązywany podczas uruchamiania head, na krytycznej ścieżce renderowania dokumentu. Na zimnej trasie opóźnia to head o kilka milisekund i może nieco pogorszyć LCP.

      Rozwiąż słownik w loader trasy i przeczytaj go z powrotem z loaderData w head. Loadery dopasowanych tras działają równolegle, a staleTime: Infinity mówi TanStack Router, że wynik nigdy się nie starzeje, więc fragment dla konkretnego locale'a jest rozwiązywany raz i podawany z cache'a routera później, pozostawiając head synchroniczny.

      src/routes/{-$locale}/index.tsx
      import { createFileRoute } from "@tanstack/react-router";
      import {
        defaultLocale,
        getIntlayerAsync,
        getLocalizedUrl,
        localeMap,
      } from "intlayer";
      
      export const Route = createFileRoute("/{-$locale}/")({
        component: RouteComponent,
        // Rozwiązywany równolegle z innymi dopasowanymi trasami, poza krytyczną ścieżką head
        loader: async ({ params }) => {
          const { locale = defaultLocale } = params;
      
          return { metaContent: await getIntlayerAsync("app", locale) };
        },
        // Słownik nigdy się nie zmienia dla danego locale: rozwiąż fragment raz
        staleTime: Infinity,
        head: ({ params, loaderData }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // Ścieżka dla tej trasy
      
          return {
            links: [
              // Link kanoniczny: wskazuje na bieżącą stronę zlokalizowaną
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: powiadomi Google o wszystkich zlokalizowanych wersjach
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: dla użytkowników w niedopasowanych językach
              // Zdefiniuj domyślny fallback locale (zwykle Twój język podstawowy)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: loaderData?.metaContent.title },
              {
                name: "description",
                content: loaderData?.metaContent.meta.description,
              },
            ],
          };
        },
      });
      
      head może być wywoływany przed osadzeniem loadera, więc loaderData jest wpisywana jako możliwie undefined. Zachowaj opcjonalne łańcuchowanie lub zwróć tytuł fallback.

      Zachowujesz fragment dla konkretnego locale'a bez płacenia jego kosztu na krytycznej ścieżce head. Cena to doświadczenie deweloperskie: zawartość musi być jawnie przekazywana z loadera do head poprzez loaderData.

      Którą rozdzielczość powinienem wybrać?

      Rozdzielczość statycznaRozdzielczość dynamicznaRozdzielczość dynamiczna z cache'em
      APIgetIntlayergetIntlayerAsync (v9.4+)getIntlayerAsync w loader (v9.4+)
      Sygnatura headsynchronicznaasyncsynchroniczna, czyta loaderData
      Ustawienia regionalnekażdy zadeklarowany localetylko żądany localetylko żądany locale
      Nawigacja na kliencienic do rozwiązaniaponownie wznawiane przy każdym dopasowaniuobsługiwane z cache'u routera
      Doświadczenie deweloperanajprostszejedno awaitzawartość przesłana przez loaderData
    14. Pobierz locale w swoich server actions

      Możesz chcieć uzyskać dostęp do bieżącego locale'a z wnętrza twoich server actions lub API endpoints. Możesz to zrobić używając helpera getLocale z intlayer.

      Oto przykład używający server functions TanStack Start:

      src/routes/{-$locale}/index.tsx
      import { createServerFn } from "@tanstack/react-start";
      import {
        getRequestHeader,
        getRequestHeaders,
      } from "@tanstack/react-start/server";
      import { getCookie, getIntlayer, getLocale } from "intlayer";
      
      export const getLocaleServer = createServerFn().handler(async () => {
        const locale = await getLocale({
          // Pobierz cookie z żądania (domyślnie: 'INTLAYER_LOCALE')
          getCookie: (name) => {
            const cookieString = getRequestHeader("cookie");
      
            return getCookie(name, cookieString);
          },
          // Pobierz nagłówek z żądania (domyślnie: 'x-intlayer-locale')
          // Fallback używający negocjacji Accept-Language
          getHeader: (name) => getRequestHeader(name),
        });
      
        // Pobierz zawartość używając getIntlayerAsync()
        const content = getIntlayer("app", locale);
      
        return { locale, content };
      });
      
    15. Zarządzanie stronami &quot;nie znaleziono&quot;

      Gdy użytkownik odwiedza nieistniejącą stronę, możesz wyświetlić niestandardową stronę "nie znaleziono", a prefiks lokalizacji może wpływać na sposób wyzwalania strony "nie znaleziono".

      Zrozumienie obsługi 404 w TanStack Router z prefiksami lokalizacji

      W TanStack Router obsługa stron 404 z zlokalizowanymi trasami wymaga podejścia wielowarstwowego:

      1. Dedykowana trasa 404: Konkretna trasa do wyświetlenia interfejsu 404
      2. Walidacja na poziomie trasy: Weryfikuje prefiksy lokalizacji i przekierowuje nieprawidłowe do 404
      3. Trasa catch-all: Przechwytuje wszystkie niedopasowane ścieżki w segmencie lokalizacji
      src/routes/{-$locale}/404.tsx
      import { createFileRoute } from "@tanstack/react-router";
      
      // To tworzy dedykowaną trasę /[locale]/404
      // Jest używana zarówno jako bezpośrednia trasa, jak i importowana jako komponent w innych plikach
      export const Route = createFileRoute("/{-$locale}/404")({
        component: NotFoundComponent,
      });
      
      // Eksportowane osobno, aby można było ponownie użyć w notFoundComponent i trasach catch-all
      export function NotFoundComponent() {
        return (
          <div>
            <h1>404</h1>
          </div>
        );
      }
      
      src/routes/{-$locale}/route.tsx
      import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
      import { validatePrefix } from "intlayer";
      import { NotFoundComponent } from "./404";
      
      export const Route = createFileRoute("/{-$locale}")({
        // beforeLoad uruchamia się przed renderowaniem trasy (zarówno na serwerze, jak i kliencie)
        // To idealne miejsce do walidacji prefiksu lokalizacji
        beforeLoad: ({ params }) => {
          const localeParam = params.locale;
      
          // validatePrefix sprawdza, czy lokalizacja jest prawidłowa zgodnie z konfiguracją intlayer
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            // Nieprawidłowy prefiks lokalizacji - przekieruj do strony 404 z prawidłowym prefiksem lokalizacji
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
            });
          }
        },
        component: Outlet,
        // notFoundComponent jest wywoływane, gdy trasa potomna nie istnieje
        // np. /en/nieistniejaca-strona wyzwala to w układzie /en
        notFoundComponent: NotFoundComponent,
      });
      
      src/routes/{-$locale}/$.tsx
      import { createFileRoute } from "@tanstack/react-router";
      
      import { NotFoundComponent } from "./404";
      
      // Trasa $ (splat/catch-all) pasuje do każdej ścieżki, która nie pasuje do innych tras
      // np. /en/jakas/gleboko/zagniezdzona/niewazna/sciezka
      // To zapewnia, że WSZYSTKIE niedopasowane ścieżki w lokalizacji wyświetlają stronę 404
      // Bez tego niedopasowane głębokie ścieżki mogą wyświetlać pustą stronę lub błąd
      export const Route = createFileRoute("/{-$locale}/$")({
        component: NotFoundComponent,
      });
      
    16. Wyodrębnij zawartość swoich komponentów

      Opcjonalne

      isOptional={true}>

      Jeśli masz istniejącą bazę kodu, transformacja tysięcy plików może być czasochłonna.

      Aby ułatwić ten proces, Intlayer proponuje kompilator / ekstraktor, aby przetransformować komponenty i wyodrębnić zawartość.

      Aby go skonfigurować, możesz dodać sekcję compiler w pliku intlayer.config.ts:

      intlayer.config.ts
      import { type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        // ... Reszta Twojej konfiguracji
        compiler: {
          /**
           * Wskazuje, czy kompilator powinien być włączony.
           */
          enabled: true,
      
          /**
           * Definiuje ścieżkę plików wyjściowych
           */
          output: ({ fileName, extension }) => `./${fileName}${extension}`,
      
          /**
           * Wskazuje, czy komponenty powinny zostać zapisane po transformacji. W ten sposób kompilator można uruchomić tylko raz, aby przetransformować aplikację, a następnie go usunąć.
           */
          saveComponents: false,
      
          /**
           * Prefiks klucza słownika
           */
          dictionaryKeyPrefix: "",
        },
      };
      
      export default config;
      

      Uruchom ekstraktor, aby przetransformować komponenty i wyodrębnić zawartość

      bash
      npx intlayer extract
      
      Since v9, the intlayerCompiler is included in the intlayer plugin. So you don't need to add it manually.

      Zaktualizuj vite.config.ts, aby dołączyć wtyczkę intlayerCompiler:

      vite.config.ts
      import { defineConfig } from "vite";
      import { intlayer, intlayerCompiler } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [
          intlayer(),
          intlayerCompiler(), // Adds the compiler plugin
        ],
      });
      
      bash
      npm run build # Lub npm run dev
      
    17. Pre-render & Generate Sitemap

      Intlayer wyposażony jest w wbudowany generator mapy witryny, który pomaga łatwo utworzyć mapę witryny dla aplikacji. Obsługuje zlokalizowane trasy i dodaje niezbędne metadane dla wyszukiwarek.

      Generowana przez Intlayer mapa witryny obsługuje przestrzeń nazw xhtml:link (Hreflang XML Extensions). W przeciwieństwie do domyślnych generatorów map witryny, które wyświetlają tylko surowe adresy URL, Intlayer automatycznie tworzy wymagane dwukierunkowe linki między wszystkimi wersjami językowymi strony (np. /about, /about?lang=fr i /about?lang=es). Zapewnia to, że wyszukiwarki prawidłowo indeksują i dostarczają odpowiednią wersję językową właściwej publiczności.

      Aby go używać, najpierw musisz skonfigurować swój plik vite.config.ts, aby włączyć pre-rendering dla zlokalizowanych tras i wyłączyć domyślne generowanie mapy witryny TanStack Start.

      vite.config.ts
      import { localeFlatMap } from "intlayer";
      // ... inne importy
      
      export const pathList = ["", "/about", "/404"];
      
      const localizedPages = localeFlatMap(({ urlPrefix }) =>
        pathList.map((path) => ({
          path: `${urlPrefix}${path}`,
          prerender: {
            enabled: true,
          },
        }))
      );
      
      export default defineConfig({
        plugins: [
          // ... pozostałe wtyczki
          tanstackStart({
            // ... pozostała konfiguracja
            sitemap: {
              enabled: false,
            },
            prerender: {
              enabled: true,
              crawlLinks: false,
              concurrency: 10,
            },
            pages: localizedPages,
          }),
        ],
      });
      

      Następnie utwórz trasę src/routes/sitemap[.]xml.ts, która wykorzystuje funkcję generateSitemap:

      src/routes/sitemap[.]xml.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { generateSitemap } from "intlayer";
      
      const SITE_URL = (
        import.meta.env.VITE_SITE_URL ?? "http://localhost:3000"
      ).replace(/\/$/, "");
      
      export const Route = createFileRoute("/sitemap.xml")({
        server: {
          handlers: {
            GET: async () => {
              const sitemap = generateSitemap(
                [
                  { path: "/", changefreq: "daily", priority: 1.0 },
                  { path: "/about", changefreq: "monthly", priority: 0.8 },
                ],
                { siteUrl: SITE_URL }
              );
      
              return new Response(sitemap, {
                headers: { "Content-Type": "application/xml" },
              });
            },
          },
        },
      });
      
    18. Konfiguruj TypeScript

      Intlayer wykorzystuje module augmentation, aby uzyskać korzyści z TypeScript i uczynić Twoją codebase mocniejszą.

      Upewnij się, że Twoja konfiguracja TypeScript zawiera autogenerowane typy:

      tsconfig.json
      {
        // ... twoje istniejące konfiguracje
        include: [
          // ... twoje istniejące includy
          ".intlayer/**/*.ts", // Dołącz auto-generowane typy
        ],
      }
      

    Konfiguracja Git

    Zaleca się ignorowanie plików generowanych przez Intlayer. Pozwala to uniknąć ich zatwierdzania do repozytorium Git.

    Aby to zrobić, możesz dodać następujące instrukcje do pliku .gitignore:

    .gitignore
    # Ignoruj pliki generowane przez Intlayer
    .intlayer
    

    Rozszerzenie VS Code

    Aby ulepszyć doświadczenie programistyczne dzięki Intlayer, możesz zainstalować oficjalne rozszerzenie Intlayer dla VS Code.

    Zainstaluj z VS Code Marketplace

    To rozszerzenie zapewnia:

    • Autouzupełnianie dla kluczy tłumaczeń.
    • Wykrywanie błędów w czasie rzeczywistym dla brakujących tłumaczeń.
    • Podglądy inline przetłumaczonej zawartości.
    • Szybkie akcje do łatwego tworzenia i aktualizacji tłumaczeń.

    Aby uzyskać więcej szczegółów na temat korzystania z rozszerzenia, zapoznaj się z dokumentacją rozszerzenia Intlayer VS Code Extension.

    Idź dalej

    Aby pójść dalej, możesz wdrożyć edytor wizualny lub externalizować swoją zawartość przy użyciu CMS.

    Referencje Dokumentacji

    Często Zadawane Pytania

    TanStack Start nie posiada własnej warstwy i18n, więc wybór sprowadza się do bibliotek:

    • i18next / react-i18next oraz react-intl: popularne biblioteki oparte na przestrzeniach nazw JSON ładowanych w runtime.
    • Intlayer: najbardziej zaawansowane rozwiązanie. Treści deklarowane w dowolnym miejscu bazy kodu (obok każdego komponentu lub centralnie) i kompilowane w czasie budowy, w pełni typowane, z tłumaczeniem AI, edytorem wizualnym i systemem CMS.

    Główną zaletą w TanStack Start jest ścisła integracja z SSR i prerenderowaniem, brak konieczności przesyłania niepotrzebnych słowników na klienta oraz autouzupełnianie TypeScript. Zobacz dlaczego Intlayer.

    Znacznie mniej niż rozwiązania oparte na przestrzeniach nazw, ponieważ strona nigdy nie pobiera katalogu, którego nie renderuje. Kompilator czasu budowy zastępuje wywołania useIntlayer dokładnymi wpisami ze słownika, których używa komponent, dzięki czemu nieużywane klucze i nieużywane języki są usuwane, a słowniki dynamiczne dzielą resztę na poszczególne języki. W porównaniu z typowymi alternatywami, Intlayer zmniejsza rozmiar bundle'a i strony nawet o 50%. Zobacz optymalizację bundle'a oraz benchmark.

    Tak, i są dwie drogi. Możesz migrować treść stopniowo za pomocą przewodnika migracji z react-i18next. Możesz także zachować obecne API: adaptery kompatybilności udostępniają dokładnie to samo API co react-i18next i react-intl, ale zasilane słownikami Intlayer.

    Tak. Wtyczka sync JSON utrzymuje Twoje pliki /messages/{locale}/{namespace}.json jako źródło prawdy i generuje z nich słowniki Intlayer w obu kierunkach. Wtyczka sync PO robi to samo dla katalogów gettext, a pliki per locale pozwalają rozdzielić zawartość według języka zamiast grupować lokalizacje w jednym pliku.

    Nie. Uruchom npx intlayer extract, a Intlayer odczyta Twoje komponenty, wyodrębni ciągi widoczne dla użytkownika i utworzy plik .content obok każdego z nich, dzięki czemu przeglądasz diff zamiast ręcznie kopiować ciągi do katalogu pojedynczo.

    W przypadku w pełni zautomatyzowanego procesu Intlayer Compiler robi to samo w czasie budowania: skanuje kod JSX, TSX, Vue i Svelte przy każdej zmianie, generuje słowniki i utrzymuje je w synchronizacji za pośrednictwem hot module replacement, dzięki czemu nie trzeba w ogóle ręcznie utrzymywać kluczy.

    Warto pamiętać o dwóch ograniczeniach przed włączeniem kompilatora. Działa on w oparciu o analizę statyczną, więc ciągi tekstowe istniejące tylko w czasie wykonywania, takie jak kody błędów API czy pola z CMS, pozostają poza jego zasięgiem. Musi on także odróżnić tekst dla użytkownika od logiki aplikacji, takiej jak className="active" czy kod stanu, co w dużej bazie kodu wymaga kilku adnotacji. Polecenie extract unika obu tych problemów, pozostawiając Ci pełną kontrolę.

    Pięć narzędzi, wszystkie opcjonalne:

    • Rozszerzenie VS Code: przejście od klucza useIntlayer do pliku treści, który go deklaruje, wyodrębnianie treści z komponentu oraz uruchamianie build, fill, test, push i pull z palety poleceń lub dedykowanej karty Intlayer.
    • Serwer LSP: taka sama świadomość w dowolnym edytorze obsługującym LSP, z funkcjami przejdź do definicji (go to definition), znajdź wszystkie referencje, podglądem przetłumaczonej wartości po najechaniu kursorem, autouzupełnianiem kluczy i pól oraz ostrzeżeniem, gdy klucz nie jest nigdzie zadeklarowany. Rozpoznaje również wywołania i18next, react-i18next, next-intl i use-intl, co ułatwia migrację.
    • Serwer MCP: udostępnia dokumentację i CLI Intlayer dla Cursor, VS Code, Claude Desktop, Claude Code i ChatGPT, dzięki czemu asystent odpowiada na podstawie aktualnej dokumentacji zamiast zgadywać i może samodzielnie wykonywać polecenia, takie jak intlayer fill.
    • Umiejętności agenta (Agent skills): wyspecjalizowane umiejętności, takie jak intlayer-config, intlayer-cli i intlayer-content, oraz po jednej dla każdego frameworka, które uczą agenta konfiguracji routingu i typów węzłów treści.
    • Wtyczka ESLint: reguła no-raw-text oznacza zakodowane na stałe ciągi tekstowe, z dodatkowymi regułami dla statycznych kluczy słownika i nieużywanej zawartości.