Автор:
    Создание:2025-09-09Последнее обновление:2026-09-20

    Переведите ваш Tanstack Start с Intlayer | Интернационализация (i18n)

    Содержание

    Это руководство демонстрирует, как интегрировать Intlayer для бесшовной интернационализации в проектах Tanstack Start с маршрутизацией, учитывающей локаль, поддержкой TypeScript и современными практиками разработки.

    Почему Intlayer лучше альтернатив?

    По сравнению с основными решениями, такими как «react-i18next», «use-intl» или «paraglide», Intlayer предлагает решение со встроенными оптимизациями, такими как:

    Intlayer полностью оптимизирован для TanStack Start и обеспечивает многоязычную маршрутизацию, управление файлами cookie, генерацию карты сайта, динамическую загрузку контента и все функции, необходимые для масштабирования ваших усилий по интернационализации (i18n).

    Вместо загрузки огромных файлов JSON на свои страницы загружайте только необходимый контент. Intlayer помогает уменьшить размер бандла и страниц до 50 %.

    Определение области содержимого вашего приложения облегчает обслуживание крупномасштабных приложений. Вы можете дублировать или удалить отдельную папку функций, не утруждав себя мысленным бременем проверки всей кодовой базы контента. Кроме того, Intlayer полностью типизирован, что обеспечивает точность вашего контента.

    Совместное размещение контента уменьшает контекст, необходимый для моделей большого языка (LLM). Intlayer также поставляется с набором инструментов, таких как CLI для проверки отсутствия переводов,LSP, MCP, и agent skills, чтобы сделать работу разработчика (DX) еще более удобной для агентов ИИ.

    Используйте автоматизацию для перевода в своем конвейере CI/CD, используя LLM по вашему выбору за счет вашего поставщика ИИ. Intlayer также предлагает компилятор для автоматизации извлечения контента, а также веб-платформу, которая помогает переводить в фоновом режиме.

    Подключение больших файлов JSON к компонентам может привести к проблемам с производительностью и реактивностью. Intlayer оптимизирует загрузку контента во время сборки (build time).

    Intlayer предлагает больше, чем просто решение i18n. Он предоставляет автономный визуальный редактор и полный CMS, чтобы помочь вам управлять многоязычным контентом в реальном времени, упрощая сотрудничество с переводчиками, копирайтерами и другими членами команды. Контент может храниться локально и/или удаленно.

    Пошаговое руководство по настройке Intlayer в приложении Tanstack Start

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

    См. Шаблон приложения на GitHub.

    1. Создайте проект

      Начните с создания нового проекта TanStack Start, следуя руководству Start new project на сайте TanStack Start.

    2. Установите пакеты Intlayer

      Установите необходимые пакеты, используя предпочитаемый менеджер пакетов:

      bash
      npx intlayer init --interactive
      
      флаг --interactive не является обязательным. Используйте intlayer-cli init, если вы являетесь ИИ-агентом.
      Эта команда определит вашу среду и установит необходимые пакеты. Например:
      bash
      npm install intlayer react-intlayer
      npm install vite-intlayer --save-dev
      
      • intlayer

        Основной пакет, предоставляющий инструменты интернационализации для управления конфигурацией, перевода, объявления контента, транспиляции и CLI-команд.

      • react-intlayer Пакет, который интегрирует Intlayer с приложением React. Он предоставляет провайдеры контекста и хуки для интернационализации в React.

      • vite-intlayer Включает плагин Vite для интеграции Intlayer с сборщиком Vite, а также промежуточное ПО для определения предпочтительной локали пользователя, управления куки и обработки перенаправления URL.

    3. Конфигурация вашего проекта

      Архитектура

      В этой архитектуре все локализованные маршруты вложены в сегмент маршрута {-$locale}. Такой подход гарантирует, что каждый язык имеет выделенный URL-адрес, обеспечивая при этом автоматическое добавление префикса локали, валидацию и 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
      

      Конфигурация

      Создайте файл конфигурации для настройки языков вашего приложения:

      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;
      
      С помощью этого файла конфигурации вы можете настроить локализованные URL, перенаправление через middleware, имена cookie, расположение и расширение ваших объявлений контента, отключить логи Intlayer в консоли и многое другое. Для полного списка доступных параметров обратитесь к документации по конфигурации.
    4. Интеграция Intlayer в вашу конфигурацию Vite

      Добавьте плагин intlayer в вашу конфигурацию:

      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;
      
      Плагин Vite intlayer() используется для интеграции Intlayer с Vite. Он обеспечивает сборку файлов деклараций контента и отслеживает их в режиме разработки. Также он определяет переменные окружения Intlayer внутри приложения Vite. Дополнительно плагин предоставляет алиасы для оптимизации производительности.
    5. Создайте корневой макет

      Настройте корневой макет для поддержки интернационализации, используя useParams для определения текущей локали и установив атрибуты lang и dir в теге 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. Создайте макет локали

      Создайте макет, который обрабатывает префикс локали и выполняет валидацию.

      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;
      
          // Валидация префикса локали
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
            });
          }
        },
        component: Outlet,
      });
      
      Здесь {-$locale}, это динамический параметр маршрута, который заменяется текущей локалью. Эта нотация делает слот необязательным, позволяя ему работать с такими режимами маршрутизации, как 'prefix-no-default' и т. д.

      Имейте в виду, что этот слот может вызвать проблемы, если вы используете несколько динамических сегментов в одном маршруте (например, /{-$locale}/other-path/$anotherDynamicPath/...). Для режима 'prefix-all' вы можете предпочесть переключить слот на $locale. Для режимов 'no-prefix' или 'search-params' вы можете полностью удалить слот.

    7. Объявите ваш контент

      Создавайте и управляйте объявлениями контента для хранения переводов:

      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;
      
      Ваши объявления контента могут быть определены в любом месте вашего приложения, как только они включены в директорию contentDir (по умолчанию, ./app). И соответствуют расширению файла объявления контента (по умолчанию, .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml}).
      Для получения дополнительной информации обратитесь к документации по объявлениям контента.
    8. Создание компонентов и хуков с поддержкой локализации

      Создайте компонент LocalizedLink для навигации с учетом локали:

      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"]}
          />
        );
      };
      

      Этот компонент выполняет две задачи:

      • Удаляет ненужный префикс {-$locale} из URL.
      • Вставляет параметр локали в URL, чтобы пользователь был напрямую перенаправлен на локализованный маршрут.

      Далее мы можем создать хук useLocalizedNavigate для программной навигации:

      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. Использование Intlayer на ваших страницах

      Используйте useIntlayer по умолчанию: это рекомендуемый способ читать контент внутри компонентов, и компилятор разрешает его в отрисовываемую локаль. Обращайтесь к getIntlayer / getIntlayerAsync только вне дерева React: в head маршрутов, загрузчиках и серверных функциях.

      Получайте доступ к вашим словарям контента по всему приложению:

      Локализованная домашняя страница

      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>
        );
      }
      

      Если вы хотите использовать ваш контент в атрибуте string, таком как alt, title, href, aria-label и т. д., вы можете использовать значение функции, например:

      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)} />
      
      Чтобы узнать больше о хуке useIntlayer, обратитесь к документации.
    10. Создание компонента переключателя локали

      Создайте компонент, позволяющий пользователям изменять языки:

      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>
                    {/* Локаль - например FR */}
                    {localeEl}
                  </span>
                  <span>
                    {/* Язык в его собственной локали - например Français */}
                    {getLocaleName(localeEl, locale)}
                  </span>
                  <span dir={getHTMLTextDir(localeEl)} lang={localeEl}>
                    {/* Язык в текущей локали - например Francés с текущей локалью установленной в Locales.SPANISH */}
                    {getLocaleName(localeEl)}
                  </span>
                  <span dir="ltr" lang={Locales.ENGLISH}>
                    {/* Язык на английском - например French */}
                    {getLocaleName(localeEl, Locales.ENGLISH)}
                  </span>
                </LocalizedLink>
              </li>
            ))}
          </ol>
        );
      };
      
      Чтобы узнать больше о хуке useLocale, обратитесь к документации.
    11. Управление атрибутами HTML

      Как показано на шаге 5, вы можете управлять атрибутами lang и dir тега html используя useParams в вашем корневом компоненте. Это гарантирует, что правильные атрибуты установлены на сервере и клиенте.

      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. Добавить middleware

      Вы также можете использовать intlayerProxy для добавления маршрутизации на стороне сервера в ваше приложение. Этот плагин автоматически определит текущую локаль на основе URL и установит соответствующий файл cookie локали. Если локаль не указана, плагин определит наиболее подходящую локаль на основе предпочтений языка браузера пользователя. Если локаль не обнаружена, он перенаправит на локаль по умолчанию.

      Обратите внимание, что для использования intlayerProxy в production вам необходимо переместить пакет vite-intlayer из devDependencies в dependencies.
      Начиная с Intlayer v9, intlayerProxy() встроен непосредственно в плагин intlayer() и включен по умолчанию через опцию routing.enableProxy (true по умолчанию). Регистрация его отдельно, как показано ниже, теперь опциональна: она сохранена для обратной совместимости и для настроек, которым нужно контролировать порядок плагинов. Установите routing.enableProxy: false чтобы отключить. Смотрите примечания к выпуску 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. Интернационализация ваших метаданных

      getIntlayer разрешает синхронно против объединённого словаря, того, который содержит каждую объявленную локаль. head остаётся синхронным и ничего не ожидается, но весь многоязычный словарь вытягивается в chunk маршрута, отправляемый в браузер.

      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 = "/"; // Путь для этого маршрута
      
          const metaContent = getIntlayer("app", locale);
      
          return {
            links: [
              // Канонический link: Указывает на текущую локализованную страницу
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: Расскажите Google обо всех локализованных версиях
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: Для пользователей на несоответствующих языках
              // Определите локаль по умолчанию (обычно ваш основной язык)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: metaContent.title },
              { name: "description", content: metaContent.meta.description },
            ],
          };
        },
      });
      

      Лучше всего для небольших словарей метаданных, нескольких локалей или при прототипировании.

      getIntlayerAsync (доступно с v9.4) ведёт себя как getIntlayer, но плагин сборки указывает его на chunk для конкретной локали в .intlayer/dynamic_dictionaries/ вместо объединённого словаря. Поэтому страница доставляет только локаль, которую она отображает. Поскольку этот chunk загружается по требованию, head становится 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 = "/"; // Путь для этого маршрута
      
          const metaContent = await getIntlayerAsync("app", locale);
      
          return {
            links: [
              // Канонический link: Указывает на текущую локализованную страницу
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: Расскажите Google обо всех локализованных версиях
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: Для пользователей на несоответствующих языках
              // Определите локаль по умолчанию (обычно ваш основной язык)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: metaContent.title },
              { name: "description", content: metaContent.meta.description },
            ],
          };
        },
      });
      
      Если head читает несколько словарей, разрешите их с помощью Promise.all: ожидание каждого getIntlayerAsync в отдельной строке цепляет запросы вместо параллельного выполнения.

      Компромисс: динамический импорт разрешается во время выполнения head, на критическом пути рендера документа. На холодном маршруте это задерживает head на несколько миллисекунд и может немного ухудшить LCP.

      Разрешите словарь в loader маршрута и прочитайте его обратно из loaderData в head. Loaders совпадающих маршрутов выполняются параллельно, и staleTime: Infinity говорит TanStack Router, что результат никогда не устаревает, поэтому chunk для конкретной локали разрешается один раз и впоследствии подается из кеша маршрутизатора, оставляя head синхронным.

      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
        loader: async ({ params }) => {
          const { locale = defaultLocale } = params;
      
          return { metaContent: await getIntlayerAsync("app", locale) };
        },
        // Словарь никогда не меняется для данной локали: разрешите chunk один раз
        staleTime: Infinity,
        head: ({ params, loaderData }) => {
          const { locale = defaultLocale } = params;
          const path = "/"; // Путь для этого маршрута
      
          return {
            links: [
              // Канонический link: Указывает на текущую локализованную страницу
              { rel: "canonical", href: getLocalizedUrl(path, locale) },
      
              // Hreflang: Расскажите Google обо всех локализованных версиях
              ...localeMap(({ locale: mapLocale }) => ({
                rel: "alternate",
                hrefLang: mapLocale,
                href: getLocalizedUrl(path, mapLocale),
              })),
      
              // x-default: Для пользователей на несоответствующих языках
              // Определите локаль по умолчанию (обычно ваш основной язык)
              {
                rel: "alternate",
                hrefLang: "x-default",
                href: getLocalizedUrl(path, defaultLocale),
              },
            ],
            meta: [
              { title: loaderData?.metaContent.title },
              {
                name: "description",
                content: loaderData?.metaContent.meta.description,
              },
            ],
          };
        },
      });
      
      head может быть вызван до того, как loader завершится, поэтому loaderData типизируется как возможно undefined. Сохраните опциональную цепочку или верните резервное название.

      Вы сохраняете chunk для конкретной локали без его стоимости на критическом пути head. Цена — это опыт разработчика: содержимое должно быть явно передано из loader в head через loaderData.

      Какое разрешение выбрать?

      Статическое разрешениеДинамическое разрешениеКэшированное динамическое разрешение
      APIgetIntlayergetIntlayerAsync (v9.4+)getIntlayerAsync in loader (v9.4+)
      head signaturesynchronousasyncsynchronous, reads loaderData
      Locales shippedevery declared localerequested locale onlyrequested locale only
      Client navigationsnothing to resolvere-entered on every matchserved from the router cache
      Developer experiencesimplestone awaitcontent threaded through loaderData
    14. Получите языковой стандарт в ваших серверных действиях

      Вы можете захотеть получить доступ к текущему языковому стандарту из ваших серверных действий или конечных точек API. Вы можете сделать это, используя помощник getLocale из intlayer.

      Вот пример использования серверных функций 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({
          // Получите cookie из запроса (по умолчанию: 'INTLAYER_LOCALE')
          getCookie: (name) => {
            const cookieString = getRequestHeader("cookie");
      
            return getCookie(name, cookieString);
          },
          // Получите заголовок из запроса (по умолчанию: 'x-intlayer-locale')
          // Резервный вариант с использованием переговоров Accept-Language
          getHeader: (name) => getRequestHeader(name),
        });
      
        // Получите некоторое содержимое, используя getIntlayerAsync()
        const content = getIntlayer("app", locale);
      
        return { locale, content };
      });
      
    15. Управление страницами &quot;не найдено&quot;

      Когда пользователь посещает несуществующую страницу, вы можете отобразить пользовательскую страницу "не найдено", и префикс локали может повлиять на способ срабатывания страницы "не найдено".

      Понимание обработки 404 в TanStack Router с префиксами локали

      В TanStack Router обработка страниц 404 с локализованными маршрутами требует многоуровневого подхода:

      1. Выделенный маршрут 404: Специфический маршрут для отображения интерфейса 404
      2. Проверка на уровне маршрута: Проверяет префиксы локали и перенаправляет недействительные на 404
      3. Маршрут catch-all: Перехватывает все несовпадающие пути в сегменте локали
      src/routes/{-$locale}/404.tsx
      import { createFileRoute } from "@tanstack/react-router";
      
      // Это создает выделенный маршрут /[locale]/404
      // Он используется как прямой маршрут и импортируется как компонент в других файлах
      export const Route = createFileRoute("/{-$locale}/404")({
        component: NotFoundComponent,
      });
      
      // Экспортируется отдельно, чтобы можно было повторно использовать в notFoundComponent и 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 выполняется до рендеринга маршрута (как на сервере, так и на клиенте)
        // Это идеальное место для проверки префикса локали
        beforeLoad: ({ params }) => {
          const localeParam = params.locale;
      
          // validatePrefix проверяет, является ли локаль действительной согласно вашей конфигурации intlayer
          const { isValid, localePrefix } = validatePrefix(localeParam);
      
          if (!isValid) {
            // Недействительный префикс локали - перенаправить на страницу 404 с действительным префиксом локали
            throw redirect({
              to: "/{-$locale}/404",
              params: { locale: localePrefix },
            });
          }
        },
        component: Outlet,
        // notFoundComponent вызывается, когда дочерний маршрут не существует
        // например, /en/несуществующая-страница запускает это в макете /en
        notFoundComponent: NotFoundComponent,
      });
      
      src/routes/{-$locale}/$.tsx
      import { createFileRoute } from "@tanstack/react-router";
      
      import { NotFoundComponent } from "./404";
      
      // Маршрут $ (splat/catch-all) соответствует любому пути, который не соответствует другим маршрутам
      // например, /en/какой-то/глубоко/вложенный/недействительный/путь
      // Это гарантирует, что ВСЕ несоответствующие пути в локали показывают страницу 404
      // Без этого несоответствующие глубокие пути могут показать пустую страницу или ошибку
      export const Route = createFileRoute("/{-$locale}/$")({
        component: NotFoundComponent,
      });
      
    16. Извлечение содержимого ваших компонентов

      Необязательно

      isOptional={true}>

      Если у вас есть существующая кодовая база, преобразование тысяч файлов может занять много времени.

      Чтобы упростить этот процесс, Intlayer предлагает компилятор / экстрактор для преобразования ваших компонентов и извлечения содержимого.

      Чтобы настроить его, вы можете добавить раздел compiler в ваш файл intlayer.config.ts:

      intlayer.config.ts
      import { type IntlayerConfig } from "intlayer";
      
      const config: IntlayerConfig = {
        // ... Остальная часть вашей конфигурации
        compiler: {
          /**
           * Указывает, должен ли быть включен компилятор.
           */
          enabled: true,
      
          /**
           * Определяет путь к выходным файлам
           */
          output: ({ fileName, extension }) => `./${fileName}${extension}`,
      
          /**
           * Указывает, должны ли компоненты сохраняться после преобразования. Таким образом, компилятор можно запустить только один раз для преобразования приложения, а затем удалить.
           */
          saveComponents: false,
      
          /**
           * Префикс ключа словаря
           */
          dictionaryKeyPrefix: "",
        },
      };
      
      export default config;
      

      Запустите экстрактор для преобразования компонентов и извлечения содержимого

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

      Обновите ваш vite.config.ts, чтобы включить плагин 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 # Или npm run dev
      
    17. Генерация карты сайта (Sitemap)

      Необязательно

      Intlayer поставляется со встроенным генератором карты сайта, который поможет вам легко создать карту сайта для вашего приложения. Он учитывает локализованные маршруты и добавляет необходимые метаданные для поисковых систем.

      Создаваемая Intlayer карта сайта поддерживает пространство имен xhtml:link (Hreflang XML Extensions). В отличие от стандартных генераторов карт сайта, которые просто перечисляют прямые URL-адреса, Intlayer автоматически создает необходимые двусторонние связи между всеми языковыми версиями страницы (например, /about, /about?lang=fr и /about?lang=es). Это гарантирует, что поисковые системы будут правильно индексировать и показывать нужную языковую версию соответствующей аудитории.

      Чтобы использовать его, вам сначала нужно настроить ваш файл vite.config.ts, чтобы включить предварительный рендеринг (pre-rendering) для ваших локализованных маршрутов и отключить генерацию карты сайта по умолчанию в TanStack Start.

      vite.config.ts
      import { localeFlatMap } from "intlayer";
      // ... другие импорты
      
      export const pathList = ["", "/about", "/404"];
      
      const localizedPages = localeFlatMap(({ urlPrefix }) =>
        pathList.map((path) => ({
          path: `${urlPrefix}${path}`,
          prerender: {
            enabled: true,
          },
        }))
      );
      
      export default defineConfig({
        plugins: [
          // ... другие плагины
          tanstackStart({
            // ... другие настройки
            sitemap: {
              enabled: false,
            },
            prerender: {
              enabled: true,
              crawlLinks: false,
              concurrency: 10,
            },
            pages: localizedPages,
          }),
        ],
      });
      

      Затем создайте маршрут src/routes/sitemap[.]xml.ts, который использует функцию generateSitemap:

      src/routes/sitemap[.]xml.ts
      import { createFileRoute } from "@tanstack/react-router";
      import { generateSitemap } from "intlayer";
      
      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: "https://example.com" }
              );
      
              return new Response(sitemap, {
                headers: { "Content-Type": "application/xml" },
              });
            },
          },
        },
      });
      
    18. Настройка TypeScript

      Intlayer использует расширение модулей (module augmentation), чтобы использовать преимущества TypeScript и сделать вашу кодовую базу более надежной.

      Убедитесь, что ваша конфигурация TypeScript включает автоматически сгенерированные типы:

      tsconfig.json
      {
        // ... ваши существующие настройки
        include: [
          // ... ваши существующие включения
          ".intlayer/**/*.ts", // Включить автоматически сгенерированные типы
        ],
      }
      

    Конфигурация Git

    Рекомендуется игнорировать файлы, сгенерированные Intlayer. Это позволит избежать их коммита в ваш Git-репозиторий.

    Чтобы сделать это, вы можете добавить следующие инструкции в ваш файл .gitignore:

    .gitignore
    # Игнорировать файлы, сгенерированные Intlayer
    .intlayer
    

    Расширение VS Code

    Чтобы улучшить опыт разработки с Intlayer, вы можете установить официальное расширение Intlayer VS Code.

    Установить из VS Code Marketplace

    Это расширение предоставляет:

    • Автодополнение для ключей переводов.
    • Обнаружение ошибок в реальном времени для отсутствующих переводов.
    • Встроенные предпросмотры переведённого контента.
    • Быстрые действия для удобного создания и обновления переводов.

    Для получения дополнительной информации об использовании расширения см. документацию расширения Intlayer VS Code.

    Дальше

    Чтобы пойти дальше, вы можете реализовать visual editor или экстернализировать ваш контент с помощью CMS.

    Ссылки на документацию

    Часто задаваемые вопросы

    TanStack Start не поставляется с собственным слоем i18n, поэтому выбор - это библиотека:

    • i18next / react-i18next и react-intl: независимые от фреймворка каталоги сообщений, вручную подключённые к маршрутизатору.
    • Lingui: сообщения ICU с этапом компиляции.
    • Intlayer: контент, объявленный рядом с каждым компонентом и скомпилированный во время сборки, с типизированными ключами, маршрутизацией с учётом локали, генерацией карты сайта, ИИ-переводом, визуальным редактором и CMS.

    Разница, которая важна в TanStack Start, - это маршрутизация и серверный рендеринг. Intlayer интегрируется с маршрутизатором на основе файлов, функцией head и этапом пре-рендеринга, вместо того чтобы оставлять вам сборку провайдера, детектора локали и карты сайта вручную. См. почему Intlayer и бенчмарк i18n для TanStack Start.

    Гораздо меньше, чем при подходе на основе пространств имён, потому что страница никогда не загружает каталог, который не отображает. Разметка, отрендеренная на сервере, разрешает свой контент на сервере, и компилятор во время сборки заменяет вызовы useIntlayer точными записями словаря, которые использует компонент, поэтому неиспользуемые ключи и неиспользуемые языки отбрасываются, а динамические словари разделяют остальное по локалям. По сравнению с обычными альтернативами Intlayer сокращает размер бандла и страницы до 50%. См. оптимизацию бандла и бенчмарк.

    Да, и есть два пути. Вы можете мигрировать контент постепенно с помощью руководства по миграции с react-i18next или руководства по миграции с i18next. Или вы можете полностью сохранить свой текущий API: адаптеры совместимости предоставляют точно такой же API, как react-i18next, react-intl и i18next, но обслуживаемый словарями Intlayer, поэтому меняются импорты, а код компонентов - нет.

    Да. Плагин синхронизации JSON сохраняет ваши файлы /messages/{locale}/{namespace}.json как источник истины и генерирует из них словари Intlayer, в обоих направлениях. Плагин синхронизации PO делает то же самое для каталогов gettext, а файлы по локали позволяют разделить контент по языкам вместо группировки локалей в одном файле.

    Нет. Запустите npx intlayer extract, и Intlayer прочитает ваши компоненты, извлечёт строки, видимые пользователю, и запишет файл .content рядом с каждым из них, так что вы просматриваете diff вместо копирования строк в каталог по одной. Шаг 15 этого руководства проводит вас через это.

    Для полностью автоматизированного конвейера Компилятор Intlayer делает то же самое во время сборки: он сканирует исходный код JSX, TSX, Vue и Svelte при каждом изменении, генерирует словари и поддерживает их синхронизацию через горячую замену модулей, поэтому вручную поддерживать ключи вообще не нужно.

    Стоит знать о двух ограничениях, прежде чем включать компилятор. Он работает через статический анализ, поэтому строки, существующие только во время выполнения, такие как коды ошибок API или поля CMS, остаются недоступными. И ему нужно отличать текст, видимый пользователю, от логики приложения вроде className="active" или кода статуса, что требует нескольких аннотаций в большой кодовой базе. Команда extract избегает обоих ограничений, оставляя вас в процессе.

    Пять компонентов, все опциональные:

    • Расширение для VS Code: переход от ключа useIntlayer к файлу контента, который его объявляет, извлечение контента из компонента и запуск build, fill, test, push и pull из палитры команд или отдельной вкладки Intlayer.
    • LSP-сервер: та же осведомлённость в любом редакторе, который говорит на LSP, с переходом к определению, поиском всех ссылок, предпросмотром переведённого значения при наведении, автодополнением ключей и полей и предупреждением, когда ключ нигде не объявлен. Он также разрешает вызовы i18next, react-i18next, next-intl и use-intl, что помогает при миграции.
    • MCP-сервер: предоставляет документацию и CLI Intlayer для Cursor, VS Code, Claude Desktop, Claude Code и ChatGPT, чтобы ассистент отвечал по актуальной документации, а не гадал, и мог сам запускать команды вроде intlayer fill.
    • Навыки агентов: сфокусированные навыки, такие как intlayer-config, intlayer-cli и intlayer-content, плюс по одному на фреймворк, которые обучают агента вашей настройке маршрутизации и типам узлов контента.
    • Плагин ESLint: no-raw-text помечает жёстко закодированные строки, с дополнительными правилами для статических ключей словаря и неиспользуемого контента.