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

    Переведите ваш сайт на SolidStart с помощью Intlayer | Интернационализация (i18n)

    www.youtube.com
    ide.intlayer.org
    intlayer-solid-start-template.vercel.app

    Содержание

    Это руководство описывает серверно-рендерящееся приложение SolidStart: определение локали происходит на этапе запроса, страницы рендерятся на сервере на нужном языке, а сигналы <html lang>, hreflang и sitemap, необходимые поисковым системам, отдаются уже на стороне сервера.

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

    По сравнению с такими решениями, как @solid-primitives/i18n или i18next, Intlayer — это решение со встроенными оптимизациями, такими как:

    Intlayer оптимизирован для идеальной работы с Solid: он предлагает компонентно-ориентированную область видимости контента, реактивные переводы и все возможности, необходимые для масштабирования интернационализации (i18n).

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

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

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

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

    Подключение огромных JSON-файлов к компонентам может привести к проблемам с производительностью и реактивностью. Intlayer оптимизирует загрузку контента на этапе сборки.

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


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

    1. Установка зависимостей

      Установите необходимые пакеты с помощью npm:

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

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

      • solid-intlayer

        Пакет, интегрирующий Intlayer с приложением Solid. Он предоставляет провайдеры контекста и хуки для интернационализации Solid.

      • vite-intlayer

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

      vite-intlayer здесь является серверной зависимостью, а не только зависимостью времени сборки: он предоставляет обработчик запросов, который запускает Nitro-сервер SolidStart. Оставить его в dependencies — безопасный вариант по умолчанию; переносить в devDependencies стоит только если вы деплоите собранную директорию .output, в которую Nitro встраивает обработчик.
    2. Настройка вашего проекта

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

      intlayer.config.ts
      import { type IntlayerConfig, Locales } from "intlayer";
      
      const config: IntlayerConfig = {
        internationalization: {
          locales: [
            Locales.ENGLISH,
            Locales.FRENCH,
            Locales.SPANISH,
            // Ваши другие локали
          ],
          defaultLocale: Locales.ENGLISH,
        },
        routing: {
          mode: "prefix-no-default",
        },
      };
      
      export default config;
      

      При prefix-no-default локаль по умолчанию обслуживается по URL без префикса:

      plaintext
      /            /about          → English  (локаль по умолчанию)
      /fr          /fr/about       → French
      /es          /es/about       → Spanish
      
      С помощью этого конфигурационного файла вы можете настроить локализованные URL, перенаправление через middleware, названия cookie, расположение и расширение деклараций контента, отключить логи Intlayer в консоли и многое другое. Полный список доступных параметров смотрите в документации по конфигурации.
    3. Интеграция Intlayer в конфигурацию Vite

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

      vite.config.ts
      import { solidStart } from "@solidjs/start/config";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [solidStart(), nitro(), intlayer()],
      });
      
      Плагин Vite intlayer() собирает файлы деклараций контента, отслеживает их в режиме разработки и определяет переменные окружения Intlayer внутри приложения. Он также предоставляет алиасы, оптимизирующие производительность.

      Маршрутизация по локали идёт вместе с плагином

      SolidStart работает на основе Nitro, и intlayer() регистрирует свой обработчик маршрутизации по локали прямо в серверном пайплайне Nitro (через опцию routing.enableProxy, включённую по умолчанию). Больше ничего подключать не нужно: на собранном сервере каждый запрос проверяется до того, как попадёт в роутер, и

      • локаль считывается из префикса URL, затем из cookie INTLAYER_LOCALE, затем из заголовка Accept-Language;
      • URL без префикса перенаправляется на локализованный вариант, если определённая локаль не является локалью по умолчанию (//fr);
      • избыточно префиксированный URL перенаправляется обратно к каноническому виду (/en/about/about);
      • cookie локали записывается обратно в ответ.
    4. Объявление вашего контента

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

      src/contents/home.content.ts
      import { type Dictionary, t } from "intlayer";
      
      const homeContent = {
        key: "home-page",
        content: {
          title: t({
            en: "Hello world!",
            fr: "Bonjour le monde !",
            es: "¡Hola mundo!",
          }),
          metaTitle: "SolidStart + Intlayer",
          metaDescription: t({
            en: "A SolidStart application internationalized with Intlayer.",
            fr: "Une application SolidStart internationalisée avec Intlayer.",
            es: "Una aplicación SolidStart internacionalizada con Intlayer.",
          }),
          documentation: t({
            en: "Visit start.solidjs.com to learn how to build SolidStart apps.",
            fr: "Visitez start.solidjs.com pour apprendre à créer des applications SolidStart.",
            es: "Visita start.solidjs.com para aprender a crear aplicaciones SolidStart.",
          }),
        },
      } satisfies Dictionary;
      
      export default homeContent;
      
      ⚠️ Особенность SolidStart: каждый файл .ts / .tsx в директории src/routes становится маршрутом, а файл .content.ts имеет экспорт по умолчанию, поэтому он тоже будет воспринят как страница. Держите декларации контента ваших страниц вне директории routes (хорошо подходит src/contents/). Контент компонентов может оставаться рядом с ними, так как src/components не сканируется файловым роутером.

      Ваши декларации контента могут быть определены в любом месте приложения, если они включены в директорию contentDir (по умолчанию ./src) и соответствуют расширению файла декларации контента (по умолчанию .content.{json,ts,tsx,js,jsx,mjs,cjs,md,mdx,yaml,yml}).

      Подробнее см. в документации по декларации контента.

    5. Добавление локализованной маршрутизации

      Цель этого шага — дать каждому языку свой собственный URL, который будет индексироваться поисковыми системами.

      Переместите ваши страницы в необязательный динамический сегмент. В файловом роутере SolidStart [[locale]] компилируется в шаблон пути :locale?:

      plaintext
      src/routes/
        [[locale]].tsx          ← layout, проверяющий сегмент
        [[locale]]/
          index.tsx             → /        и /fr        и /es
          about.tsx             → /about   и /fr/about  и /es/about
        [...404].tsx            → catch-all для всего остального
      

      Единственная задача файла layout — ограничить сегмент настроенной локалью:

      src/routes/[[locale]].tsx
      import type { RouteSectionProps } from "@solidjs/router";
      import { locales } from "intlayer";
      
      export const route = {
        matchFilters: {
          locale: locales,
        },
      };
      
      export default function LocaleLayout(props: RouteSectionProps) {
        return <>{props.children}</>;
      }
      

      @solidjs/router разворачивает :locale? в два шаблона — один с сегментом и один без — и пробует их по убыванию специфичности. Именно matchFilters отличает работающую настройку от запутывающей:

      URL Без matchFilters С matchFilters
      /fr/about Страница about на французском Страница about на французском
      /about Страница about (статический сегмент побеждает) Страница about
      /unknown Главная страница, незаметно, с locale=unknown Нет совпадения → переходит к catch-all 404
      Предпочитайте [locale] (обязательный) вместо [[locale]], если вы используете режим маршрутизации 'prefix-all', и полностью уберите сегмент для 'no-prefix' или 'search-params'.
    6. Передача локали вашему приложению

      URL — это единственный источник истины для локали: middleware уже перенаправил запрос на его локализованный путь, поэтому чтение пути в корневом layout сохраняет согласованность серверного рендеринга и клиентской гидратации, а также автоматически обновляет локаль при каждой клиентской навигации.

      src/app.tsx
      import { MetaProvider } from "@solidjs/meta";
      import { Router, useLocation } from "@solidjs/router";
      import { FileRoutes } from "@solidjs/start/router";
      import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";
      import { IntlayerProvider } from "solid-intlayer";
      import { createEffect, type ParentProps, Suspense } from "solid-js";
      import { isServer } from "solid-js/web";
      import { Nav } from "~/components/Nav";
      import "./app.css";
      
      const RootLayout = (props: ParentProps) => {
        const location = useLocation();
        const locale = () => getLocaleFromPath(location.pathname) ?? defaultLocale;
      
        // Сервер рендерит <html> в entry-server.tsx; клиентским переходам между
        // локалями нужно самостоятельно обновлять атрибуты.
        createEffect(() => {
          if (isServer) return;
      
          document.documentElement.lang = locale();
          document.documentElement.dir = getHTMLTextDir(locale());
        });
      
        return (
          <MetaProvider>
            <IntlayerProvider locale={locale()}>
              <Nav />
              <Suspense>{props.children}</Suspense>
            </IntlayerProvider>
          </MetaProvider>
        );
      };
      
      export default function App() {
        return (
          <Router root={RootLayout}>
            <FileRoutes />
          </Router>
        );
      }
      
      IntlayerProvider реагирует на свой проп locale, поэтому достаточно передать вызов accessor'а locale() внутри JSX — Solid компилирует его в геттер, и всё дерево перерендеривается на новом языке при изменении URL.
    7. Установка атрибутов lang и dir HTML на сервере

      Элемент <html> рендерится в entry-server.tsx, вне Router. Вместо этого считывайте локаль из URL запроса:

      src/entry-server.tsx
      // @refresh reload
      import { createHandler, StartServer } from "@solidjs/start/server";
      import { defaultLocale, getHTMLTextDir, getLocaleFromPath } from "intlayer";
      import { getRequestEvent } from "solid-js/web";
      
      export default createHandler(() => (
        <StartServer
          document={({ assets, children, scripts }) => {
            const url = getRequestEvent()?.request.url ?? "/";
            const locale = getLocaleFromPath(url) ?? defaultLocale;
      
            return (
              <html dir={getHTMLTextDir(locale)} lang={locale}>
                <head>
                  <meta charset="utf-8" />
                  <meta
                    name="viewport"
                    content="width=device-width, initial-scale=1"
                  />
                  <link rel="icon" href="/favicon.ico" />
                  {assets}
                </head>
                <body>
                  <div id="app">{children}</div>
                  {scripts}
                </body>
              </html>
            );
          }}
        />
      ));
      

      Теперь краулеры получают правильный язык уже в первом байте:

      html
      <html dir="ltr" lang="fr"></html>
      
    8. Использование Intlayer на ваших страницах

      Обращайтесь к словарям контента в любом месте приложения:

      src/routes/[[locale]]/index.tsx
      import { Meta, Title } from "@solidjs/meta";
      import { useIntlayer } from "solid-intlayer";
      import Counter from "~/components/Counter";
      
      export default function Home() {
        const content = useIntlayer("home-page");
      
        return (
          <main>
            <Title>{content.metaTitle.value}</Title>
            <Meta content={content.metaDescription.value} name="description" />
            <h1>{content.title}</h1>
            <Counter />
            <p>{content.documentation}</p>
          </main>
        );
      }
      
      В Solid useIntlayer возвращает реактивный контент (например, content). Вы можете обращаться к его свойствам напрямую.

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

      html
      <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 см. в документации.

      Контентные узлы не ограничиваются простыми переводами. Например, счётчик с формами множественного числа:

      src/components/Counter.content.ts
      import { type Dictionary, plural, t } from "intlayer";
      
      const counterContent = {
        key: "counter",
        content: {
          clicks: plural({
            one: t({
              en: "{{count}} click",
              fr: "{{count}} clic",
              es: "{{count}} clic",
            }),
            other: t({
              en: "{{count}} clicks",
              fr: "{{count}} clics",
              es: "{{count}} clics",
            }),
          }),
        },
      } satisfies Dictionary;
      
      export default counterContent;
      
      src/components/Counter.tsx
      import { useIntlayer } from "solid-intlayer";
      import { createSignal } from "solid-js";
      
      export default function Counter() {
        const [count, setCount] = createSignal(0);
        const content = useIntlayer("counter");
      
        return (
          <button onClick={() => setCount(count() + 1)} type="button">
            {content.clicks(count())}
          </button>
        );
      }
      

      plural() выбирает категорию через Intl.PluralRules для активной локали, поэтому языки с более чем двумя формами множественного числа работают без дополнительного кода.

    9. Создание компонента локализованной ссылки

      Создайте пользовательский компонент Link, который автоматически добавляет префикс языка к внутренним URL:

      src/components/LocalizedLink.tsx
      import { A, type AnchorProps } from "@solidjs/router";
      import { getLocalizedUrl } from "intlayer";
      import { useLocale } from "solid-intlayer";
      import type { ParentComponent } from "solid-js";
      
      export const LocalizedLink: ParentComponent<AnchorProps> = (props) => {
        const { locale } = useLocale();
      
        const isExternal = () => /^[a-z][a-z0-9+.-]*:/i.test(props.href);
      
        const localizedHref = () =>
          isExternal() ? props.href : getLocalizedUrl(props.href, locale());
      
        return <A {...props} href={localizedHref()} />;
      };
      
      src/components/Nav.tsx
      import { useIntlayer } from "solid-intlayer";
      import type { Component } from "solid-js";
      import { LocaleSwitcher } from "./LocaleSwitcher";
      import { LocalizedLink } from "./LocalizedLink";
      
      export const Nav: Component = () => {
        const content = useIntlayer("nav");
      
        return (
          <nav>
            <LocalizedLink href="/">{content.home}</LocalizedLink>
            <LocalizedLink href="/about">{content.about}</LocalizedLink>
            <LocaleSwitcher />
          </nav>
        );
      };
      

      Написав href="/about" один раз, вы получаете /about, /fr/about или /es/about в зависимости от активной локали — без ручного добавления префиксов где-либо на страницах.

    10. Создание компонента переключателя локали

      Отрисовывайте переключатель как настоящие ссылки, а не как <select>: каждый язык текущей страницы становится индексируемой ссылкой, которую можно открыть в новой вкладке — то, что элемент управления только на JavaScript предложить не может.

      getPathWithoutLocale убирает сегмент локали из текущего пути, а getLocalizedUrl перестраивает его для целевой локали, поэтому ссылки следуют вашему режиму маршрутизации без жёсткого кодирования чего-либо. Именно навигация меняет отображаемую локаль — маршрут [[locale]] определяет её из URL — в то время как setLocale сохраняет выбор в cookie INTLAYER_LOCALE, чтобы при последующем посещении URL без локали открывался тот же язык.

      src/components/LocaleSwitcher.tsx
      import { A, useLocation } from "@solidjs/router";
      import {
        getHTMLTextDir,
        getLocaleName,
        getLocalizedUrl,
        getPathWithoutLocale,
      } from "intlayer";
      import { useIntlayer, useLocale } from "solid-intlayer";
      import { type Component, For } from "solid-js";
      
      export const LocaleSwitcher: Component = () => {
        const content = useIntlayer("locale-switcher");
        const location = useLocation();
        const { locale, setLocale, availableLocales } = useLocale();
      
        // Канонический (без локали) путь текущей отображаемой страницы
        const pathWithoutLocale = () => getPathWithoutLocale(location.pathname);
      
        return (
          <div>
            <button
              aria-label={content.label.value}
              popoverTarget="localePopover"
              type="button"
            >
              {getLocaleName(locale())}
            </button>
            <div id="localePopover" popover="auto">
              <For each={availableLocales}>
                {(localeItem) => (
                  <A
                    dir={getHTMLTextDir(localeItem)}
                    // Точное совпадение, чтобы ссылка на локаль по умолчанию не
                    // помечалась активной на каждой странице
                    end
                    href={getLocalizedUrl(pathWithoutLocale(), localeItem)}
                    hreflang={localeItem}
                    lang={localeItem}
                    onClick={() => setLocale(localeItem)}
                    // Гарантирует, что кнопка "назад" браузера вернёт на предыдущую страницу
                    replace
                  >
                    {/* Язык на своём собственном языке — например, Français */}
                    {getLocaleName(localeItem)}
                  </A>
                )}
              </For>
            </div>
          </div>
        );
      };
      

      В Solid locale из useLocale — это accessor-сигнал. Используйте locale() (со скобками), чтобы реактивно прочитать его текущее значение.

      getLocaleName(localeItem) отображает каждый язык на его собственном языке — English / Français / Español. Передайте второй аргумент, чтобы перевести названия на текущий отображаемый язык: getLocaleName(localeItem, locale()) даёт English / French / Spanish на английском, anglais / français / espagnol на французском.

      <A> уже устанавливает aria-current="page" на ссылке, соответствующей текущему URL, так что здесь ничего добавлять не нужно. replace считывается роутером обратно из отрисованного атрибута: он заменяет запись в истории вместо добавления новой, поэтому кнопка "назад" браузера возвращает на страницу, посещённую до переключения, а не на ту же страницу на предыдущем языке.

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

      Подробнее о хуке useLocale см. в документации.

    11. Отправка ссылок canonical и hreflang

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

      Аннотации hreflang сообщают поисковым системам, что /about, /fr/about и /es/about — это одна и та же страница на разных языках. getMultilingualUrls формирует их из канонического (без локали) пути в соответствии с вашим режимом маршрутизации, поэтому ничего не приходится жёстко кодировать:

      src/components/AlternateLinks.tsx
      import {
        defaultLocale,
        getMultilingualUrls,
        getPathWithoutLocale,
      } from "intlayer";
      import { type Component, For } from "solid-js";
      
      export type AlternateLinksProps = {
        /** Абсолютный URL отображаемой страницы. */
        url: string;
      };
      
      export const AlternateLinks: Component<AlternateLinksProps> = (props) => {
        const multilingualUrls = () => {
          const { origin, pathname } = new URL(props.url);
      
          return Object.entries(
            getMultilingualUrls(`${origin}${getPathWithoutLocale(pathname)}`)
          );
        };
      
        const canonicalUrl = () =>
          new URL(props.url).origin + new URL(props.url).pathname;
      
        return (
          <>
            <link href={canonicalUrl()} rel="canonical" />
            <For each={multilingualUrls()}>
              {([locale, localizedUrl]) => (
                <link href={localizedUrl} hreflang={locale} rel="alternate" />
              )}
            </For>
            <link
              href={
                multilingualUrls().find(([locale]) => locale === defaultLocale)?.[1]
              }
              hreflang="x-default"
              rel="alternate"
            />
          </>
        );
      };
      

      Отрисуйте его в head документа, где доступен URL запроса:

      src/entry-server.tsx
      import { AlternateLinks } from "~/components/AlternateLinks";
      
      // … внутри <head>, рядом с остальными мета-тегами:
      <AlternateLinks url={url} />;
      

      GET /fr/about отдаёт тогда:

      html
      <link href="https://example.com/fr/about" rel="canonical" />
      <link href="https://example.com/about" hreflang="en" rel="alternate" />
      <link href="https://example.com/fr/about" hreflang="fr" rel="alternate" />
      <link href="https://example.com/es/about" hreflang="es" rel="alternate" />
      <link href="https://example.com/about" hreflang="x-default" rel="alternate" />
      
      Примечание о @solidjs/meta: на момент написания <Title> и <Meta> из @solidjs/meta применяются на клиенте после гидратации, но не попадают в серверно-рендерённый <head> в SolidStart v2. Пока это не исправлено выше по стеку, отрисовывайте теги, которые краулеры должны видеть без JavaScript — canonical, hreflang и, при необходимости, title / description — напрямую в entry-server.tsx, как показано выше.
    12. Управление страницами 404

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

      Splat-маршрут в корне src/routes перехватывает все пути, которые не совпали с сегментом локали, включая некорректные префиксы локали, отклонённые matchFilters. Так как локаль по-прежнему определяется из URL через корневой layout, страница 404 отображается на языке посетителя:

      src/routes/[...404].tsx
      import { Title } from "@solidjs/meta";
      import { HttpStatusCode } from "@solidjs/start";
      import { useIntlayer } from "solid-intlayer";
      import { LocalizedLink } from "~/components/LocalizedLink";
      
      export default function NotFound() {
        const content = useIntlayer("not-found-page");
      
        return (
          <main>
            <Title>{content.metaTitle.value}</Title>
            <HttpStatusCode code={404} />
            <h1>{content.title}</h1>
            <LocalizedLink href="/">{content.backHome}</LocalizedLink>
          </main>
        );
      }
      
      Запрос Результат
      /xx 404xx не является настроенной локалью
      /nonexistent 404 на локали по умолчанию
      /fr/nonexistent 404 на французском (Page introuvable)
    13. Генерация многоязычного sitemap

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

      Генератор sitemap Intlayer разворачивает каждый путь в одну запись на каждую локаль и связывает альтернативы через xhtml:link между ними, так что маршрут должен перечислять только канонические пути без локали.

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

      SolidStart превращает файл, экспортирующий HTTP-метод, в API-маршрут и убирает расширение .ts из пути — поэтому src/routes/sitemap.xml.ts обслуживается по адресу /sitemap.xml:

      src/routes/sitemap.xml.ts
      import type { APIEvent } from "@solidjs/start/server";
      import { generateSitemap } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      export const GET = (_event: APIEvent) => {
        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" },
        });
      };
      
      output of GET /sitemap.xml
      <?xml version="1.0" encoding="UTF-8"?>
      <urlset
        xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml"
      >
        <url>
          <loc>https://example.com/about</loc>
          <changefreq>monthly</changefreq>
          <priority>0.8</priority>
          <xhtml:link rel="alternate" hreflang="en" href="https://example.com/about"/>
          <xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/about"/>
          <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/about"/>
          <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about"/>
        </url>
      </urlset>
      
      API-маршруты не поддерживают необязательные параметры, поэтому держите этот файл в корне src/routes, вне сегмента [[locale]]. Sitemap уже содержит все локали.

      Аналогично можно собрать robots.txt с помощью getMultilingualUrls, чтобы записи Disallow охватывали все локализованные написания чувствительного пути:

      src/routes/robots.txt.ts
      import { getMultilingualUrls } from "intlayer";
      
      const SITE_URL = process.env.SITE_URL ?? "http://localhost:3000";
      
      const disallowedPaths = ["/admin", "/private"].flatMap((path) =>
        Object.values(getMultilingualUrls(path))
      );
      
      export const GET = () =>
        new Response(
          [
            "User-agent: *",
            "Allow: /",
            ...disallowedPaths.map((path) => `Disallow: ${path}`),
            "",
            `Sitemap: ${SITE_URL}/sitemap.xml`,
          ].join("\n"),
          { headers: { "Content-Type": "text/plain" } }
        );
      
    14. Получение локали в серверных функциях

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

      Иногда нужно получить доступ к текущей локали внутри серверной функции или API-маршрута.

      В настройке на основе префиксов, как эта, URL является авторитетным источником: getLocaleFromPath считывает префикс из URL запроса. getLocale — это резервный вариант для запросов без префикса локали: он проверяет cookie INTLAYER_LOCALE, затем заголовок x-intlayer-locale, затем согласовывает Accept-Language.

      src/routes/[[locale]]/index.tsx
      import { createAsync } from "@solidjs/router";
      import { getCookie, getIntlayer, getLocale, getLocaleFromPath } from "intlayer";
      import { getRequestEvent } from "solid-js/web";
      
      const loadLocalizedData = async () => {
        "use server";
      
        const request = getRequestEvent()?.request;
      
        const locale =
          getLocaleFromPath(request?.url) ??
          (await getLocale({
            // Получить cookie из запроса (по умолчанию: 'INTLAYER_LOCALE')
            getCookie: (name) =>
              getCookie(name, request?.headers.get("cookie") ?? ""),
            // Получить заголовок из запроса (по умолчанию: 'x-intlayer-locale'),
            // с резервом на согласование Accept-Language
            getHeader: (name) => request?.headers.get(name) ?? undefined,
          }));
      
        // Получить некоторый контент вне компонента с помощью getIntlayer()
        const content = getIntlayer("home-page", locale);
      
        return { locale, title: String(content.title) };
      };
      
      export default function Page() {
        const data = createAsync(() => loadLocalizedData());
      
        return <p>{data()?.title}</p>;
      }
      
      Не полагайтесь только на getLocale здесь: cookie локали записывается только после того, как посетитель активно переключит язык, поэтому первый визит на /fr/... разрешился бы в локаль по умолчанию.
    15. Извлечение контента ваших компонентов

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

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

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

      Чтобы настроить это, добавьте секцию compiler в ваш файл intlayer.config.ts:

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

      Запустите экстрактор, чтобы преобразовать ваши компоненты и извлечь контент

      bash
      npx intlayer extract
      
      Впоследствии переместите сгенерированные файлы контента ваших страниц из src/routes — по причине, объяснённой в шаге 5.
      Начиная с v9, intlayerCompiler включён в плагин intlayer. Так что добавлять его вручную не нужно.

      Обновите ваш vite.config.ts, включив плагин intlayerCompiler:

      vite.config.ts
      import { solidStart } from "@solidjs/start/config";
      import { nitro } from "nitro/vite";
      import { defineConfig } from "vite";
      import { intlayer, intlayerCompiler } from "vite-intlayer";
      
      export default defineConfig({
        plugins: [
          solidStart({ middleware: "src/middleware.ts" }),
          nitro(),
          intlayer(),
          intlayerCompiler(), // Добавляет плагин компилятора
        ],
      });
      
      bash
      npm run build # Или npm run dev
      
    16. Настройка TypeScript

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

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

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

      Теперь ключи словарей и пути контента проверяются во время компиляции:

      tsx
      useIntlayer("home-page"); // ✅
      useIntlayer("hom-page"); // ❌ Argument of type '"hom-page"' is not assignable to parameter of type 'keyof __DictionaryRegistry'
      

    Проверка вашей настройки

    Соберите и запустите сервер, затем проверьте, что следующие запросы ведут себя как ожидается:

    bash
    npm run build
    node .output/server/index.mjs
    
    Запрос Ожидаемый ответ
    GET / 200 — английский
    GET / с Accept-Language: fr 302/fr
    GET / с cookie INTLAYER_LOCALE=es 302/es
    GET /fr 200 — французский, <html lang="fr">
    GET /fr/about 200 — страница about на французском
    GET /en/about 302/about (канонический редирект)
    GET /xx 404
    GET /fr/nonexistent 404 на французском
    GET /sitemap.xml 200 — многоязычный XML sitemap

    Строки, отображающие страницу, ведут себя идентично под vite dev. Три строки с редиректами применяются только к собранному серверу, если только вы сами не зарегистрируете обработчик как middleware — см. шаг 3.

    Запускайте dev-сервер на Node (vite dev), а не на Bun (bun --bun vite dev): SSR SolidStart в настоящее время падает под рантаймом Bun с ошибкой Expected a Response object, but received 'NodeResponse'. Это не связано с Intlayer — ошибка воспроизводится на обычном шаблоне — и затрагивает только dev-сервер, а не vite build.

    Настройка Git

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

    Для этого добавьте следующие инструкции в файл .gitignore:

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

    Расширение для VS Code

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

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

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

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

    Что дальше

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


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